// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 垃圾收集器：清扫

// 清扫器由两种不同的算法组成：
// 
// /*对象回收器查找并释放跨中未标记的插槽。如果没有标记任何对象，它可以释放整个跨度，但这不是它的目标。这可以由
// mcentral同步驱动。用于mcentral span的cacheSpan，或由
// sweepone异步执行，后者查看所有mcentral列表。
// 
// *跨度回收器查找不包含标记对象的跨度
// 并释放整个跨度。这是一个单独的算法，因为
// 释放整个跨度是对象回收器最困难的任务，
// 但在分配新跨度时非常关键。
// 这里是mheap_2;。回收并由
// /的顺序扫描驱动，该页面在堆区域中标记位图。
// 
// 这两种算法最终都会调用mspan。sweep，它扫描单个
// 堆跨度。

package runtime

import (
	"runtime/internal/atomic"
	"unsafe"
)

var sweep sweepdata

// 背景扫描状态。
type sweepdata struct {
	lock    mutex
	g       *g
	parked  bool
	started bool

	nbgsweep    uint32
	npausesweep uint32

	// 活动跟踪未完成的清理程序和清理
	// 终止条件。
	active activeSweep

	// centralIndex是当前未扫描的span类。
	// 它表示进入mcentral span的索引
	// 集合。通过其加载和
	// 更新方法访问和更新。没有锁的。
	// 
	// 在标记终止时重置。
	// 由mheap使用。下一步我们要哭了。
	centralIndex sweepClass
}

// sweepClass是一个span类，一个位代表我们当前是部分还是全部span。
type sweepClass uint32

const (
	numSweepClasses            = numSpanClasses * 2
	sweepClassDone  sweepClass = sweepClass(^uint32(0))
)

func (s *sweepClass) load() sweepClass {
	return sweepClass(atomic.Load((*uint32)(s)))
}

func (s *sweepClass) update(sNew sweepClass) {
	// 仅在当前值小于sNew时更新*s，
	// 因为*s单调增加。这不是唯一一个我们有原子单调递增计数器的地方。
	sOld := s.load()
	for sOld < sNew && !atomic.Cas((*uint32)(s), uint32(sOld), uint32(sNew)) {
		sOld = s.load()
	}
	// 最好有一个“atomic max”，它刚刚在大多数架构上实现了
	// 如上所述。但是，有些架构
	// 比如RISC-V本身就支持atomic max。
}

func (s *sweepClass) clear() {
	atomic.Store((*uint32)(s), 0)
}

// split返回基础的span类以及
// 我们是否对该类的完整或部分
// 未扫描列表感兴趣，表示为布尔
// （true表示“完整”）。
func (s sweepClass) split() (spc spanClass, full bool) {
	return spanClass(s >> 1), s&1 == 0
}

// nextSpanForSweep从
// 中央扫描缓冲区查找并弹出下一个扫描范围。它将跨度的所有权返回给调用者。
// 如果不存在这样的跨度，则返回nil。
func (h *mheap) nextSpanForSweep() *mspan {
	sg := h.sweepgen
	for sc := sweep.centralIndex.load(); sc < numSweepClasses; sc++ {
		spc, full := sc.split()
		c := &h.central[spc].mcentral
		var s *mspan
		if full {
			s = c.fullUnswept(sg).pop()
		} else {
			s = c.partialUnswept(sg).pop()
		}
		if s != nil {
			// 写下我们发现了一些东西，这样未来的清洁工
			// 就可以从这里开始了。
			sweep.centralIndex.update(sc)
			return s
		}
	}
	// 写下我们什么也没找到。
	sweep.centralIndex.update(sweepClassDone)
	return nil
}

const sweepDrainedMask = 1 << 31

// activeSweep是一种类型，用于捕获是否完成了清理
// 以及是否有任何未完成的清理程序。
// 
// 每个潜在的清扫者必须在寻找工作前调用begin（），在清扫结束后调用end（）。
type activeSweep struct {
	// 国家分为两部分。
	// 
	// 顶部位（被sweepDrainedMask屏蔽）是一个布尔值
	// 值，指示是否已从队列中排出所有扫描工作
	// 值。
	// 
	// 其余的位是一个计数器，指示
	// 未完成的并发清理程序的数量。
	state atomic.Uint32
}

// 开始注册一个新的清扫器。返回一个sweepLocker 
// 用于获取扫描跨度。任何未完成的清扫器阻塞
// 清扫终止。
// 
// 如果sweepLocker无效，调用方可以确保所有
// 未完成的扫描工作都已耗尽，因此没有剩余的
// 要扫描的内容。请注意，当前可能有清扫器正在运行，因此
// 这并不表示所有清扫都已完成。
// 
// 即使sweepLocker无效，其sweepGen也始终有效。
func (a *activeSweep) begin() sweepLocker {
	for {
		state := a.state.Load()
		if state&sweepDrainedMask != 0 {
			return sweepLocker{mheap_.sweepgen, false}
		}
		if a.state.CompareAndSwap(state, state+1) {
			return sweepLocker{mheap_.sweepgen, true}
		}
	}
}

// 结束取消清扫器的注册。如果sweepLocker有效，则每次调用
// begin时必须调用一次。
func (a *activeSweep) end(sl sweepLocker) {
	if sl.sweepGen != mheap_.sweepgen {
		throw("sweeper left outstanding across sweep generations")
	}
	for {
		state := a.state.Load()
		if (state&^sweepDrainedMask)-1 >= sweepDrainedMask {
			throw("mismatched begin/end of activeSweep")
		}
		if a.state.CompareAndSwap(state, state-1) {
			if state != sweepDrainedMask {
				return
			}
			if debug.gcpacertrace > 0 {
				print("pacer: sweep done at heap size ", gcController.heapLive>>20, "MB; allocated ", (gcController.heapLive-mheap_.sweepHeapLiveBasis)>>20, "MB during sweep; swept ", mheap_.pagesSwept.Load(), " pages at ", mheap_.sweepPagesPerByte, " pages/byte\n")
			}
			return
		}
	}
}

// markdefied将活动扫描循环标记为已排空
// 所有剩余工作。这是安全的，可以与所有其他activeSweep方法同时调用
// 尽管可能存在竞争。
// 
// 如果此调用是实际执行
// 标记的调用，则返回true。
func (a *activeSweep) markDrained() bool {
	for {
		state := a.state.Load()
		if state&sweepDrainedMask != 0 {
			return false
		}
		if a.state.CompareAndSwap(state, state|sweepDrainedMask) {
			return true
		}
	}
}

// 清扫器返回当前活动清扫器的数量。
func (a *activeSweep) sweepers() uint32 {
	return a.state.Load() &^ sweepDrainedMask
}

// 如果所有清扫工作都已排干，并且没有更多的
// 未完成的清扫者存在，则isDone返回true。也就是说，当扫描阶段为
// /完全完成时。
func (a *activeSweep) isDone() bool {
	return a.state.Load() == sweepDrainedMask
}

// 重置为下一个扫描周期设置活动扫描。世界必须被阻止。
func (a *activeSweep) reset() {
	assertWorldStopped()
	a.state.Store(0)
}

// finishsweep_m确保所有跨度都被扫掠。世界必须被阻止。这确保了
// 进程中没有扫描。
// 
// go:nowritebarrier 
func finishsweep_m() {
	assertWorldStopped()

	// 标记开始前必须完成清扫，因此
	// 清扫所有未清扫的跨距。如果这是一个并发GC，那么
	// 不应该有任何跨度需要扫描，所以这应该立即完成
	// 。如果在并发扫描
	// 完成之前强制执行GC，则可能需要扫描跨距。
	for sweepone() != ^uintptr(0) {
		sweep.npausesweep++
	}

	// 确保没有任何未完成的清扫者。此时，随着世界的停止，这意味着两件事中的一件。要么我们抢占了扫地机，要么扫地机没有呼叫扫地机。忙碌的该结束的时候就结束。
	// 这两种情况都表明存在错误，所以抛出。
	if sweep.active.sweepers() != 0 {
		throw("active sweepers found at start of mark phase")
	}

	// 重置所有未扫描的缓冲区，缓冲区应为空。
	// 在扫描终止中执行此操作，而不是标记终止
	// 这样我们就可以捕捉未拉伸的跨距，并尽快将块回收为
	// 了。清扫完毕，如果清道夫还没有醒过来，就叫醒它。在这个
	sg := mheap_.sweepgen
	for i := range mheap_.central {
		c := &mheap_.central[i].mcentral
		c.partialUnswept(sg).reset()
		c.fullUnswept(sg).reset()
	}

	// /点上，它肯定有工作要做。
	wakeScavenger()

	nextMarkBitArenaEpoch()
}

func bgsweep(c chan int) {
	sweep.g = getg()

	lockInit(&sweep.lock, lockRankSweep)
	lock(&sweep.lock)
	sweep.parked = true
	c <- 1
	goparkunlock(&sweep.lock, waitReasonGCSweepWait, traceEvGoBlock, 1)

	for {
		for sweepone() != ^uintptr(0) {
			sweep.nbgsweep++
			Gosched()
		}
		for freeSomeWbufs(true) {
			Gosched()
		}
		lock(&sweep.lock)
		if !isSweepDone() {
			// 如果GC在
			// gosweepone返回
			// 上方^0和正在获取的锁之间运行，则可能发生这种情况。
			unlock(&sweep.lock)
			continue
		}
		sweep.parked = true
		goparkunlock(&sweep.lock, waitReasonGCSweepWait, traceEvGoBlock, 1)
	}
}

// sweepLocker获得了spans的扫描所有权。
type sweepLocker struct {
	// sweepGen是堆的扫描生成。
	sweepGen uint32
	valid    bool
}

// sweepLocked表示一个跨度的扫描所有权。
type sweepLocked struct {
	*mspan
}

// 尝试获取span s的扫描所有权。如果成功获取所有权，则会阻止扫描完成。
func (l *sweepLocker) tryAcquire(s *mspan) (sweepLocked, bool) {
	if !l.valid {
		throw("use of invalid sweepLocker")
	}
	// 尝试CAS前请检查。
	if atomic.Load(&s.sweepgen) != l.sweepGen-2 {
		return sweepLocked{}, false
	}
	// 尝试获取s的扫描所有权。
	if !atomic.Cas(&s.sweepgen, l.sweepGen-2, l.sweepGen-1) {
		return sweepLocked{}, false
	}
	return sweepLocked{s}, true
}

// sweepone扫描一些未扫描的堆范围，并返回返回给堆的页数
// 如果没有要扫描的内容，则返回^uintptr（0）。ABCFDG＊＊＊＊＊＊＊＊/增量锁，以确保GOODUTIN不被抢占ABCFDG 
func sweepone() uintptr {
	gp := getg()

	gp.m.locks++

	// 将清扫柜抬到呼叫者体内。
	sl := sweep.active.begin()
	if !sl.valid {
		gp.m.locks--
		return ^uintptr(0)
	}

	// 找一个跨度来清扫。
	npages := ^uintptr(0)
	var noMoreWork bool
	for {
		s := mheap_.nextSpanForSweep()
		if s == nil {
			noMoreWork = sweep.active.markDrained()
			break
		}
		if state := s.state.get(); state != mSpanInUse {
			// 如果直接扫掠已经扫掠了此跨度，则可能会发生这种情况，但在这种情况下，扫掠
			// 世代应始终是最新的。
			if !(s.sweepgen == sl.sweepGen || s.sweepgen == sl.sweepGen+3) {
				print("runtime: bad span s.state=", state, " s.sweepgen=", s.sweepgen, " sweepgen=", sl.sweepGen, "\n")
				throw("non in-use span in unswept list")
			}
			continue
		}
		if s, ok := sl.tryAcquire(s); ok {
			// 扫描我们找到的跨度。
			npages = s.npages
			if s.sweep(false) {
				// 整个跨度被释放。将其计入
				// 页面回收者信用，因为这些页面现在可以用于范围分配。
				mheap_.reclaimCredit.Add(npages)
			} else {
				// Span仍在使用中，因此这不会将任何
				// 页面返回到堆中，Span需要
				// 移动到正在使用的扫描列表中。
				npages = 0
			}
			break
		}
	}
	sweep.active.end(sl)

	if noMoreWork {
		// 扫描列表为空。可能仍在运行
		// 并发扫描，但我们至少非常
		// 即将完成扫描。

		// 将清道夫向前移动（表示有新工作要做）并唤醒清道夫。
		// 
		// 清洗器由最后一个清洗器发出信号，因为一旦完成了
		// 清洗器，我们肯定会为
		// 清洗器做有用的工作，因为清洗器每GC循环只在
		// 堆上运行一次。在扫描
		// 终止期间不会执行此更新，因为在某些情况下，在扫描完成和扫描终止之间可能会有很长的延迟
		// 例如，没有足够的
		// 分配来触发GC，这将很好地填充
		// 清除工作。
		systemstack(func() {
			lock(&mheap_.lock)
			mheap_.pages.scavengeStartGen()
			unlock(&mheap_.lock)
		})
		// 由于我们可能会在分配路径中进行扫描，因此我们不可能通过WakeCleaver直接唤醒清道夫，因为
		// 它可以进行分配。请sysmon为我们做这件事。
		readyForScavenger()
	}

	gp.m.locks--
	return npages
}

// isSweepDone报告是否扫描了所有跨度。
// 
// 请注意，当清扫器运行时，此条件可能会在任何
// 时间从假变为真。如果
// GC运行，则可能会从true转换为false；为了防止调用方必须是不可抢占的，或者必须
// 以某种方式阻止GC进程。
func isSweepDone() bool {
	return sweep.active.isDone()
}

// 仅当扫描完跨度s时返回。
// go:nowritebarrier 
func (s *mspan) ensureSwept() {
	// 调用方必须禁用抢占。
	// 否则，当此函数返回时，span可能会再次变为未扫描状态
	// （如果GC在另一个goroutine上触发）。
	_g_ := getg()
	if _g_.m.locks == 0 && _g_.m.mallocing == 0 && _g_ != _g_.m.g0 {
		throw("mspan.ensureSwept: m is not locked")
	}

	// 如果此操作失败，则意味着没有更多的跨度需要扫描。在这种情况下，要么s已经
	// 被扫描，要么即将被获取进行扫描和扫描。
	sl := sweep.active.begin()
	if sl.valid {
		// 调用者必须确保span是mSpanInUse span。
		if s, ok := sl.tryAcquire(s); ok {
			s.sweep(false)
			sweep.active.end(sl)
			return
		}
		sweep.active.end(sl)
	}

	// 不幸的是，我们不能自己扫过这个跨度。其他人
	// 先到的。我们没有有效的方法等待，但这就是
	// 好的，它很快就会被扫除。
	for {
		spangen := atomic.Load(&s.sweepgen)
		if spangen == sl.sweepGen || spangen == sl.sweepGen+3 {
			break
		}
		osyield()
	}
}

// Sweep为标记阶段中未标记的块释放或收集终结器。
// 它清除标记位，为下一轮GC做准备。
// 如果跨度被返回到堆，则返回true。
// 如果preserve=true，则不要将其返回到堆中，也不要在mcentral列表中重新链接；
// 呼叫方负责。ABCFDG＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊
func (sl *sweepLocked) sweep(preserve bool) bool {
	_g_ := getg()
	if _g_.m.locks == 0 && _g_.m.mallocing == 0 && _g_ != _g_.m.g0 {
		throw("mspan.sweep: m is not locked")
	}

	s := sl.mspan
	if !preserve {
		// 我们将释放此跨度的所有权。Nil out to 
		// 防止调用方意外使用它。
		sl.mspan = nil
	}

	sweepgen := mheap_.sweepgen
	if state := s.state.get(); state != mSpanInUse || s.sweepgen != sweepgen-1 {
		print("mspan.sweep: state=", state, " sweepgen=", s.sweepgen, " mheap.sweepgen=", sweepgen, "\n")
		throw("mspan.sweep: bad span state")
	}

	if trace.enabled {
		traceGCSweepSpan(s.npages * _PageSize)
	}

	mheap_.pagesSwept.Add(int64(s.npages))

	spc := s.spanclass
	size := s.elemsize

	// allocBits表示哪些未标记的对象不需要被处理，因为它们在上一个GC循环结束时是空闲的
	// 并且从那时起没有被分配。
	// 如果allocBits索引>=s.freeindex，且未标记位
	// 则自上次GC以来，该对象保持未分配
	// 状态。
	// 这种情况类似于在自由名单上。

	// 为我们即将释放的任何对象取消链接并释放特殊记录。
	// 这里有两个并发症：
	// 1。对象可以同时具有终结器和配置文件特殊记录。
	// 在这种情况下，我们需要将终结器排队执行，
	// 将对象标记为活动，并保留特殊配置文件。
	// 2。一个小对象可以为不同的偏移设置多个终结器。
	// 如果没有标记这样的对象，我们需要一次将所有终结器排队。
	// 1和2都可以同时使用。
	hadSpecials := s.specials != nil
	siter := newSpecialsIter(s)
	for siter.valid() {
		// 可以为对象的内部字节“查找对象开头”设置终结器。
		objIndex := uintptr(siter.s.offset) / size
		p := s.base() + objIndex*size
		mbits := s.markBitsForIndex(objIndex)
		if !mbits.isMarked() {
			// 此对象未标记且至少有一条特殊记录。
			// 通过1:查看它是否至少有一个终结器。
			hasFin := false
			endOffset := p - s.base() + size
			for tmp := siter.s; tmp != nil && uintptr(tmp.offset) < endOffset; tmp = tmp.next {
				if tmp.kind == _KindSpecialFinalizer {
					// 如果对象有终结器，请停止释放该对象。
					mbits.setMarkedNonAtomic()
					hasFin = true
					break
				}
			}
			// 通过2:将所有终结器排队或处理配置文件记录。
			for siter.valid() && uintptr(siter.s.offset) < endOffset {
				// 查找特殊设置的确切字节
				// （与对象开头相反）。
				special := siter.s
				p := s.base() + uintptr(special.offset)
				if special.kind == _KindSpecialFinalizer || !hasFin {
					siter.unlinkAndNext()
					freeSpecial(special, unsafe.Pointer(p), size)
				} else {
					// 该对象有终结器，因此我们将使其保持活动状态。
					// 所有其他特殊功能仅在对象被释放时适用，
					// 所以只需保留特殊记录即可。
					siter.next()
				}
			}
		} else {
			// 对象仍处于活动状态
			if siter.s.kind == _KindSpecialReachable {
				special := siter.unlinkAndNext()
				(*specialReachable)(unsafe.Pointer(special)).reachable = true
				freeSpecial(special, unsafe.Pointer(p), size)
			} else {
				// 保留特殊记录
				siter.next()
			}
		}
	}
	if hadSpecials && s.specials == nil {
		spanHasNoSpecials(s)
	}

	if debug.allocfreetrace != 0 || debug.clobberfree != 0 || raceenabled || msanenabled || asanenabled {
		// 查找所有新释放的对象。这不需要
		// 高效；allocfreetrace有巨大的开销。
		mbits := s.markBitsForBase()
		abits := s.allocBitsForIndex(0)
		for i := uintptr(0); i < s.nelems; i++ {
			if !mbits.isMarked() && (abits.index < s.freeindex || abits.isMarked()) {
				x := s.base() + i*s.elemsize
				if debug.allocfreetrace != 0 {
					tracefree(unsafe.Pointer(x), size)
				}
				if debug.clobberfree != 0 {
					clobberfree(unsafe.Pointer(x), size)
				}
				if raceenabled {
					racefree(unsafe.Pointer(x), size)
				}
				if msanenabled {
					msanfree(unsafe.Pointer(x), size)
				}
				if asanenabled {
					asanpoison(unsafe.Pointer(x), size)
				}
			}
			mbits.advance()
			abits.advance()
		}
	}

	// 检查僵尸对象。
	if s.freeindex < s.nelems {
		// 所有<freeindex都已分配，因此
		// 不能是僵尸。
		// 
		// 检查第一个位图字节，其中必须是
		// 小心使用freeindex。
		obj := s.freeindex
		if (*s.gcmarkBits.bytep(obj / 8)&^*s.allocBits.bytep(obj / 8))>>(obj%8) != 0 {
			s.reportZombies()
		}
		// 检查剩余字节。
		for i := obj/8 + 1; i < divRoundUp(s.nelems, 8); i++ {
			if *s.gcmarkBits.bytep(i)&^*s.allocBits.bytep(i) != 0 {
				s.reportZombies()
			}
		}
	}

	// 计算此范围内的自由对象数。
	nalloc := uint16(s.countAlloc())
	nfreed := s.allocCount - nalloc
	if nalloc > s.allocCount {
		// 上面的僵尸检查应该在
		// 中捕捉到更多细节。
		print("runtime: nelems=", s.nelems, " nalloc=", nalloc, " previous allocCount=", s.allocCount, " nfreed=", nfreed, "\n")
		throw("sweep increased allocation count")
	}

	s.allocCount = nalloc
	s.freeindex = 0 // 将分配索引重置为范围的开始。
	if trace.enabled {
		getg().m.p.ptr().traceReclaimed += uintptr(nfreed) * s.elemsize
	}

	// gcmarkBits成为allocBits。
	// 获取新的已清除gcmarkBits，为下一个GC做准备
	s.allocBits = s.gcmarkBits
	s.gcmarkBits = newMarkBits(s.nelems)

	// 初始化alloc位缓存。
	s.refillAllocCache(0)

	// 在我们更新sweepgen之前，span必须归我们独家所有，
	// 检查潜在的种族。
	if state := s.state.get(); state != mSpanInUse || s.sweepgen != sweepgen-1 {
		print("mspan.sweep: state=", state, " sweepgen=", s.sweepgen, " mheap.sweepgen=", sweepgen, "\n")
		throw("mspan.sweep: bad span state after sweep")
	}
	if s.sweepgen == sweepgen+1 || s.sweepgen == sweepgen+3 {
		throw("swept cached span")
	}

	// 我们只需要在扫描所有块时设置s.sweepgen=h.sweepgen，
	// 因为可能会有一个并发的free/SetFinalizer。
	// 
	// 但我们需要先设置它，然后才能使跨度可用于分配
	// （将其返回到heap或mcentral），因为分配代码假定如果可用于分配，
	// 跨度已被扫描。
	// 
	// 序列化点。
	// 此时标记位被清除，分配就绪
	// 开始，所以释放span。
	atomic.Store(&s.sweepgen, sweepgen)

	if spc.sizeclass() != 0 {
		// 小对象的手柄跨距。
		if nfreed > 0 {
			// 如果我们释放了任何
			// 对象，只需将跨度标记为需要调零，因为分配到
			// 中的新跨度没有完全填满，但在扫掠之后，其所有
			// 空闲插槽仍为零。
			s.needzero = 1
			stats := memstats.heapStats.acquire()
			atomic.Xadduintptr(&stats.smallFreeCount[spc.sizeclass()], uintptr(nfreed))
			memstats.heapStats.release()
		}
		if !preserve {
			// 调用者可能没有从任何
			// 未删除的设置中删除此span，但通过更新sweepgen获得了
			// 扫描的span的所有权。如果此范围仍在
			// 未扫描的集合中，则mcentral会将其从
			// 集合中弹出，检查其扫描根，并忽略它。
			if nalloc == 0 {
				// Free完全空闲的跨度直接返回堆。
				mheap_.freeSpan(s)
				return true
			}
			// 将span返回到右侧的mcentral列表。
			if uintptr(nalloc) == s.nelems {
				mheap_.central[spc].mcentral.fullSwept(sweepgen).push(s)
			} else {
				mheap_.central[spc].mcentral.partialSwept(sweepgen).push(s)
			}
		}
	} else if !preserve {
		// 大型对象的句柄跨度。
		if nfreed != 0 {
			// 将大对象范围释放到堆。

			// 注意（rsc，dvyukov）：在CL22060046中，efence 
			// 的最初实现使用了sysFree而不是sysFault，这样
			// 操作系统最终会将内存
			// 再次返回给我们，这样efence程序就可以在不耗尽内存的情况下运行
			// 更长时间。不幸的是，在这里调用sysFree而不对堆数据结构进行任何调整，意味着当内存返回时，我们的元数据错误，无论是在mspan结构中还是在垃圾收集位图中。
			// 在这里使用sysFault意味着程序在efence模式下会很快耗尽
			// 内存，但至少不会因为内存重用混乱而导致神秘的崩溃。
			// 如果我们也实现并调用某种mheap，那么应该可以切换回sysFree。删除跨度。
			if debug.efence > 0 {
				s.limit = 0 // 防止mlookup找到此跨度
				sysFault(unsafe.Pointer(s.base()), size)
			} else {
				mheap_.freeSpan(s)
			}
			stats := memstats.heapStats.acquire()
			atomic.Xadduintptr(&stats.largeFreeCount, 1)
			atomic.Xadduintptr(&stats.largeFree, size)
			memstats.heapStats.release()
			return true
		}

		// 直接在完整+扫描列表中添加一个大跨度。
		mheap_.central[spc].mcentral.fullSwept(sweepgen).push(s)
	}
	return false
}

// reportZombies报告s和抛出中的任何标记但免费的对象。
// 
// 这通常意味着以下之一：
// 
// 1。用户代码将指针转换为一个uintptr，然后不安全地返回
// 并且当uintptr是对
// 对象的唯一引用时运行GC。
// 
// 2。用户代码（或编译器错误）构造了一个坏指针，
// 指向一个空闲槽，通常是一个超过结束指针的槽。
// 
// 3。两个周期前，GC丢失了一个指针，并释放了一个活动对象
// 但它在上一个周期中仍然处于活动状态，所以这个GC周期找到了一个指向该对象的
// 指针并对其进行了标记。
func (s *mspan) reportZombies() {
	printlock()
	print("runtime: marked free object in span ", s, ", elemsize=", s.elemsize, " freeindex=", s.freeindex, " (bad use of unsafe.Pointer? try -d=checkptr)\n")
	mbits := s.markBitsForBase()
	abits := s.allocBitsForIndex(0)
	for i := uintptr(0); i < s.nelems; i++ {
		addr := s.base() + i*s.elemsize
		print(hex(addr))
		alloc := i < s.freeindex || abits.isMarked()
		if alloc {
			print(" alloc")
		} else {
			print(" free ")
		}
		if mbits.isMarked() {
			print(" marked  ")
		} else {
			print(" unmarked")
		}
		zombie := mbits.isMarked() && !alloc
		if zombie {
			print(" zombie")
		}
		print("\n")
		if zombie {
			length := s.elemsize
			if length > 1024 {
				length = 1024
			}
			hexdumpWords(addr, addr+length, nil)
		}
		mbits.advance()
		abits.advance()
	}
	throw("found pointer to free object")
}

// deductSweepCredit为分配范围
// size spanBytes而扣除扫描信用。必须在*分配范围
// 之前执行此操作，以确保系统具有足够的信用。如有必要，它将进行清扫，以防止负债。如果调用者将
// 也扫描页面（例如，对于较大的分配），则它可以传递
// 非零调用者sweepPages，使许多页面保持未扫描状态。
// 
// deductSweepCredit在最坏情况下假设，最终分配的跨度中的所有跨字节
// 字节将可用于对象
// 分配。
// 
// DeclutctSweepCredit是“比例扫描”系统的核心。
// 它使用垃圾收集器收集的统计信息执行
// 足够的扫描，以便在GC循环之间的并发
// 扫描阶段扫描所有页面。
// 
// mheap_u不能被锁定。
func deductSweepCredit(spanBytes uintptr, callerSweepPages uintptr) {
	if mheap_.sweepPagesPerByte == 0 {
		// 完成或禁用比例扫描。
		return
	}

	if trace.enabled {
		traceGCSweepStart()
	}

retry:
	sweptBasis := mheap_.pagesSweptBasis.Load()

	// 必要时修复债务。
	newHeapLive := uintptr(atomic.Load64(&gcController.heapLive)-mheap_.sweepHeapLiveBasis) + spanBytes
	pagesTarget := int64(mheap_.sweepPagesPerByte*float64(newHeapLive)) - int64(callerSweepPages)
	for pagesTarget > int64(mheap_.pagesSwept.Load()-sweptBasis) {
		if sweepone() == ^uintptr(0) {
			mheap_.sweepPagesPerByte = 0
			break
		}
		if mheap_.pagesSweptBasis.Load() != sweptBasis {
			// 扫描速度已更改。重新计算债务。
			goto retry
		}
	}

	if trace.enabled {
		traceGCSweepDone()
	}
}

// clobberfree将x处的内存内容设置为坏内容，用于调试
// 目的。
func clobberfree(x unsafe.Pointer, size uintptr) {
	// 大小（span.elemsize）始终是4的倍数。
	for i := uintptr(0); i < size; i += 4 {
		*(*uint32)(add(x, i)) = 0xdeadbeef
	}
}

// GCPaceSwipper更新清扫器的起搏参数。
// 
// 必须在更新GC的速度时调用。
// 
// 世界必须被阻止，否则就是灾难。锁必须锁好。
func gcPaceSweeper(trigger uint64) {
	assertWorldStoppedOrLockHeld(&mheap_.lock)

	// 更新扫描速度。
	if isSweepDone() {
		mheap_.sweepPagesPerByte = 0
	} else {
		// 并发扫描需要在分配的堆到达GC 
		// 页面。计算分配的每个字节中正在使用的页面与扫描
		// 触发器时扫描所有正在使用的
		// 的比率，考虑到
		// 某些页面可能已经被扫描。
		heapLiveBasis := atomic.Load64(&gcController.heapLive)
		heapDistance := int64(trigger) - int64(heapLiveBasis)
		// 添加一点边距，这样舍入错误和
		// 并发扫描在GC启动时不太可能使页面未被扫描。
		heapDistance -= 1024 * 1024
		if heapDistance < _PageSize {
			// 避免将扫描比设置得过高
			heapDistance = _PageSize
		}
		pagesSwept := mheap_.pagesSwept.Load()
		pagesInUse := mheap_.pagesInUse.Load()
		sweepDistancePages := int64(pagesInUse) - int64(pagesSwept)
		if sweepDistancePages <= 0 {
			mheap_.sweepPagesPerByte = 0
		} else {
			mheap_.sweepPagesPerByte = float64(sweepDistancePages) / float64(heapDistance)
			mheap_.sweepHeapLiveBasis = heapLiveBasis
			// 最后写入PagesWeptBasis，因为此
			// 会向并发扫描发送信号，以重新计算其债务。
			mheap_.pagesSweptBasis.Store(pagesSwept)
		}
	}
}
